
#include "RtBase.h"
//#include "RtHttpChunkedDecoder.h"

template<typename ParserT>
CRtHttpChunkedDecoder<ParserT>::CRtHttpChunkedDecoder(ParserT *pParser)
	: m_nState(StateChunkSize)
	, m_uCurrentChunkSize(0)
	, m_bHaveChunkDataCR(FALSE)
	, m_pParser(pParser)
{
	m_strLatestPartialChunkContent.reserve(16200);
	m_strLineBuf.reserve(2024);
}

template<typename ParserT>
CRtHttpChunkedDecoder<ParserT>::~CRtHttpChunkedDecoder()
{
}

template<typename ParserT> BOOL CRtHttpChunkedDecoder<ParserT>::
HandleChunkedContent(LPCSTR aBuf, DWORD aTotalLen, CRtString &aBufOut, DWORD &aReadOut)
{
	BOOL rv = FALSE;
	DWORD dwTotalLenBackup = aTotalLen;
	RT_ASSERTE_RETURN(aBuf, rv);
	DWORD dwLen = aTotalLen;
	aTotalLen += m_strLatestPartialChunkContent.length();
	m_strLatestPartialChunkContent.append(aBuf, dwLen);
	aBuf = const_cast<LPCSTR>(m_strLatestPartialChunkContent.c_str());

	
	// from RFC2617 section 3.6.1, the chunked transfer coding is defined as:
    //
    //   Chunked-Body    = *chunk
    //                     last-chunk
    //                     trailer
    //                     CRLF
    //   chunk           = chunk-size [ chunk-extension ] CRLF
    //                     chunk-data CRLF
    //   chunk-size      = 1*HEX
    //   last-chunk      = 1*("0") [ chunk-extension ] CRLF
    //       
    //   chunk-extension = *( ";" chunk-ext-name [ "=" chunk-ext-val ] )
    //   chunk-ext-name  = token
    //   chunk-ext-val   = token | quoted-CRtString
    //   chunk-data      = chunk-size(OCTET)
    //   trailer         = *(entity-header CRLF)
    //
    // the chunk-size field is a CRtString of hex digits indicating the size of the
    // chunk.  the chunked encoding is ended by any chunk whose size is zero, 
    // followed by the trailer, which is terminated by an empty line.

	while (aTotalLen) {
		const char *pLine = NULL;
		if (m_nState == StateChunkSize || m_nState == StateTrailer) {
			pLine = (const char*)(memchr(aBuf, '\n', aTotalLen));
			if (pLine) {
				DWORD nAppendLen = pLine - aBuf;
				if (pLine > aBuf && *(pLine - 1) == '\r')
					nAppendLen--;
				m_strLineBuf.append(aBuf, nAppendLen);
				LTrimString(m_strLineBuf, CRtIsSpace());

				aTotalLen -= (pLine + 1 - aBuf);
				//aBuf = pLine + 1;
				m_strLatestPartialChunkContent.erase(0, pLine + 1 - aBuf);
			} else {
				if (aBuf[aTotalLen - 1] == '\r')
					aTotalLen--;
				m_strLineBuf.append(aBuf, aTotalLen);
				aTotalLen = 0;
				break;
			}
		}

		switch (m_nState) {
		case StateChunkSize: {
			int nSemi = m_strLineBuf.find(';', 0);
			if (nSemi != -1)
				m_strLineBuf.erase(nSemi, m_strLineBuf.length() - nSemi);	// remove chunk-extension
		
			m_uCurrentChunkSize = 0;
			if (!sscanf(m_strLineBuf.c_str(), "%x", &m_uCurrentChunkSize)) {
				RT_ERROR_TRACE("CRtHttpChunkedDecoder::HandleChunkedContent,"
					" sscanf() failed! str=" << m_strLineBuf);
				return FALSE;
			}
			if (m_uCurrentChunkSize)
				m_nState = StateChunkData;
			else
			{
				m_nState = StateTrailer;
				if(m_pParser) m_pParser->OnParseFinished();
			}
			m_strLineBuf.erase(0, m_strLineBuf.length());
			m_bHaveChunkDataCR = FALSE;
			break;
							 }

		case StateChunkData: {
			if (m_uCurrentChunkSize == 0) {
				if (!m_bHaveChunkDataCR) {
					if (*aBuf == '\r') {
						m_strLatestPartialChunkContent.erase(0, 1);
						//++aBuf;
						--aTotalLen;
						m_bHaveChunkDataCR = TRUE;
						if (aTotalLen == 0)
							continue;
					}
					else {
						RT_ERROR_TRACE("CRtHttpChunkedDecoder::HandleChunkedContent,"
							"*aBuf != '\r', aBuf=" << aBuf);
						return FALSE;
					}
				}
				if (*aBuf != '\n') {
					RT_ERROR_TRACE("CRtHttpChunkedDecoder::HandleChunkedContent,"
						"*aBuf != '\n', aBuf=" << aBuf);
					return FALSE;
				}
				//++aBuf;
				m_strLatestPartialChunkContent.erase(0, 1);
				--aTotalLen;
				m_nState = StateChunkSize;
			}
			else {
				DWORD uAppendLen = aTotalLen;
				if (aTotalLen >= m_uCurrentChunkSize) {
					uAppendLen = m_uCurrentChunkSize;
					m_uCurrentChunkSize = 0;

					aBufOut.append(aBuf, uAppendLen);
					m_strLatestPartialChunkContent.erase(0, uAppendLen);
					//aBuf += uAppendLen;
					aTotalLen -= uAppendLen;
				}
				else{
					aTotalLen = 0;
				//	m_uCurrentChunkSize -= aTotalLen;
				}	
			}
			break;
							 }

		case StateTrailer: {
			if (m_strLineBuf.empty()) {
				m_nState = StateEof;
			} else {
				rv = m_HeaderArray.ParseHeaderLine(m_strLineBuf.c_str());
				RT_ASSERTE(rv);
				m_strLineBuf.erase(0, m_strLineBuf.length());
			}
			break;
						   }

		case StateEof: {
			// in order to compute the bytes read.
//			aTotalLen = 0;
			goto finish;
			break;
					   }

		default: {
			RT_ASSERTE(FALSE);
			break;
				 }
		}	// switch (m_nState)
	}	// while (aTotalLen)

finish:
	aReadOut = dwTotalLenBackup;
	return TRUE;
}


template<typename ParserT> BOOL CRtHttpChunkedDecoder<ParserT>
::HandleEveryChunkedContent(
	LPCSTR aBuf, 
	DWORD aTotalLen, 
	std::vector<CRtString> &ChunkContentList,
	DWORD &aReadOut)
{
	BOOL rv = FALSE;
	RT_ASSERTE_RETURN(aBuf, rv);
	DWORD dwTotalLenBackup = aTotalLen;
	DWORD dwLen = aTotalLen;
	aTotalLen += m_strLatestPartialChunkContent.length();
	m_strLatestPartialChunkContent.append(aBuf, dwLen);
	aBuf = const_cast<LPCSTR>(m_strLatestPartialChunkContent.c_str());

	while (aTotalLen) {
		const char *pLine = NULL;
		if (m_nState == StateChunkSize || m_nState == StateTrailer) {
			pLine = (const char*)(memchr(aBuf, '\n', aTotalLen));
			if (pLine) {
				DWORD nAppendLen = pLine - aBuf;
				if (pLine > aBuf && *(pLine - 1) == '\r')
					nAppendLen--;
				m_strLineBuf.append(aBuf, nAppendLen);
				LTrimString(m_strLineBuf, CRtIsSpace());

				aTotalLen -= (pLine + 1 - aBuf);
	//			aBuf = pLine + 1;
				m_strLatestPartialChunkContent.erase(0, pLine + 1 - aBuf);
			} else {
				if (aBuf[aTotalLen - 1] == '\r')
					aTotalLen--;
	//			m_strLineBuf.append(aBuf, aTotalLen);
				aTotalLen = 0;
				break;
			}
		}

		switch (m_nState) {
		case StateChunkSize: {
			int nSemi = m_strLineBuf.find(';', 0);
			if (nSemi != -1)
				m_strLineBuf.erase(nSemi, m_strLineBuf.length() - nSemi);	// remove chunk-extension
		
			m_uCurrentChunkSize = 0;
			if (!sscanf(m_strLineBuf.c_str(), "%lx", &m_uCurrentChunkSize)) {
				RT_ERROR_TRACE("CRtHttpChunkedDecoder::HandleChunkedContent,"
					" sscanf() failed! str=" << m_strLineBuf);
				return FALSE;
			}
			if (m_uCurrentChunkSize)
				m_nState = StateChunkData;
			else
			{
				if(m_pParser) m_pParser->OnParseFinished();
				m_nState = StateTrailer;
			}
			m_strLineBuf.erase(0, m_strLineBuf.length());
			m_bHaveChunkDataCR = FALSE;
			break;
		}

		case StateChunkData: {
			if (m_uCurrentChunkSize == 0) {
				if (!m_bHaveChunkDataCR) {
					if (*aBuf == '\r') {
						//++aBuf;
						m_strLatestPartialChunkContent.erase(0, 1);
						--aTotalLen;
						m_bHaveChunkDataCR = TRUE;
						if (aTotalLen == 0)
							continue;
					}
					else {
						RT_ERROR_TRACE("CRtHttpChunkedDecoder::HandleChunkedContent,"
							"*aBuf != '\r', *aBuf=" << *aBuf);
						return FALSE;
					}
				}
				if (*aBuf != '\n') {
					RT_ERROR_TRACE("CRtHttpChunkedDecoder::HandleChunkedContent,"
						"*aBuf != '\n', *aBuf=" << *aBuf);
					return FALSE;
				}
				m_strLatestPartialChunkContent.erase(0, 1);
//				++aBuf;
				--aTotalLen;
				m_nState = StateChunkSize;
			}
			else {
				DWORD uAppendLen = aTotalLen;
				if (aTotalLen >= m_uCurrentChunkSize) {
					uAppendLen = m_uCurrentChunkSize;
					m_uCurrentChunkSize = 0;
					CRtString str(aBuf, uAppendLen);
					m_strLatestPartialChunkContent.erase(0, uAppendLen);
					ChunkContentList.push_back(str);
					//aBuf += uAppendLen;
					aTotalLen -= uAppendLen;
				} 
				else {
					aTotalLen = 0;
				}
			}
			break;
		 }

		case StateTrailer: {
			if (m_strLineBuf.empty()) {
				m_nState = StateEof;
			} else {
				rv = m_HeaderArray.ParseHeaderLine(m_strLineBuf.c_str());
				RT_ASSERTE(rv);
				m_strLineBuf.erase(0, m_strLineBuf.length());
			}
			break;
		   }

		case StateEof: {
			// in order to compute the bytes read.
//			aTotalLen = 0;
			goto finish;
			break;
					   }

		default: {
			RT_ASSERTE(FALSE);
			break;
				 }
		}	// switch (m_nState)
	}	// while (aTotalLen)

finish:
	aReadOut = dwTotalLenBackup;// - aTotalLen;
	return TRUE;
}

template<typename ParserT> BOOL CRtHttpChunkedDecoder<ParserT>::
ReachedEOF()
{
	return m_nState == StateEof;
}
